]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
ScreenShield: show notifications in the locked screen
authorGiovanni Campagna <gcampagna@src.gnome.org>
Tue, 22 May 2012 22:02:00 +0000 (00:02 +0200)
committerGiovanni Campagna <gcampagna@src.gnome.org>
Sat, 21 Jul 2012 13:40:28 +0000 (15:40 +0200)
Track screen lock status in the message tray, and filter banner
notifications. The message tray is completely hidden when the screen is
locked, but exceptions can be made for individual transient notifications,
such as shell messages and the on screen keyboard.
Non transient sources are shown in the middle of the lock screen. Resident
notifications (such as those from Rhythmbox) are shown in full, while
persistent ones are displayed as icon and message count.

https://bugzilla.gnome.org/show_bug.cgi?id=619955

data/theme/gnome-shell.css
js/ui/main.js
js/ui/messageTray.js
js/ui/overview.js
js/ui/screenShield.js

index 25cddd3f433ee86fce3b5955e9c27526830640f6..01c0e7b80d2bbc973f6b4a5a42b5fac5195f8607 100644 (file)
@@ -2221,3 +2221,30 @@ StScrollBar StButton#vhandle:hover
 .screen-shield-clock-date {
     font-size: 48px;
 }
+
+#screenShieldNotifications {
+    border-radius: 24px;
+    background-color: rgba(0.0, 0.0, 0.0, 0.9);
+    border: 2px solid #868686;
+}
+
+#screenShieldNotifications, .screen-shield-notifications-box {
+    spacing: 8px;
+}
+
+.screen-shield-notification-source {
+    padding: 24px 8px;
+    spacing: 5px;
+}
+
+.screen-shield-notification-label {
+    font-size: 1.2em;
+    font-weight: bold;
+}
+
+/* Remove background from notifications, otherwise
+   opacity is doubled and they look darker
+*/
+.screen-shield-notifications-box .notification {
+    background-color: transparent;
+}
index 319fb7b81b2ccb3bd17ccdcd0d6acf106751c8f6..2dc152f6327002e262b176e8239be6dcf1ddc74a 100644 (file)
@@ -535,6 +535,7 @@ function notify(msg, details) {
     messageTray.add(source);
     let notification = new MessageTray.Notification(source, msg, details);
     notification.setTransient(true);
+    notification.setShowWhenLocked(true);
     source.notify(notification);
 }
 
index 393b305863f85f8709531d89853d80ec716f55e6..d1c726de88035c8e74bd07d080695c25ccb8884a 100644 (file)
@@ -420,6 +420,7 @@ const Notification = new Lang.Class({
         // 'transient' is a reserved keyword in JS, so we have to use an alternate variable name
         this.isTransient = false;
         this.expanded = false;
+        this.showWhenLocked = false;
         this._destroyed = false;
         this._useActionIcons = false;
         this._customContent = false;
@@ -816,6 +817,14 @@ const Notification = new Lang.Class({
         this.isTransient = isTransient;
     },
 
+    setShowWhenLocked: function(show) {
+        if (show && !this.isTransient) {
+            throw new Error('ShowWhenLocked can only be set on a transient notification');
+        }
+
+        this.showWhenLocked = show;
+    },
+
     setUseActionIcons: function(useIcons) {
         this._useActionIcons = useIcons;
     },
@@ -1237,6 +1246,10 @@ const Source = new Lang.Class({
     // Default implementation is to destroy this source, but subclasses can override
     _lastNotificationRemoved: function() {
         this.destroy();
+    },
+
+    hasResidentNotification: function() {
+        return this.notifications.some(function(n) { return n.resident; });
     }
 });
 Signals.addSignalMethods(Source.prototype);
@@ -1578,6 +1591,9 @@ const MessageTray = new Lang.Class({
                 }
             }));
 
+        this._isScreenLocked = false;
+        Main.screenShield.connect('lock-status-changed', Lang.bind(this, this._onScreenLockStatusChanged));
+
         this._summaryItems = [];
         // We keep a list of new summary items that were added to the summary since the last
         // time it was shown to the user. We automatically show the summary to the user if there
@@ -1684,6 +1700,12 @@ const MessageTray = new Lang.Class({
         // *first* and not show the summary item until after it hides.
         // So postpone calling _updateState() a tiny bit.
         Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { this._updateState(); return false; }));
+
+        this.emit('summary-item-added', summaryItem);
+    },
+
+    getSummaryItems: function() {
+        return this._summaryItems;
     },
 
     _onSourceDestroy: function(source) {
@@ -1758,6 +1780,11 @@ const MessageTray = new Lang.Class({
             this._notificationQueue.splice(index, 1);
     },
 
+    _onScreenLockStatusChanged: function(screenShield, locked) {
+        this._isScreenLocked = locked;
+        this._updateState();
+    },
+
     _lock: function() {
         this._locked = true;
     },
@@ -2088,18 +2115,36 @@ const MessageTray = new Lang.Class({
     // at the present time.
     _updateState: function() {
         // Notifications
-        let notificationUrgent = this._notificationQueue.length > 0 && this._notificationQueue[0].urgency == Urgency.CRITICAL;
-        let notificationsPending = this._notificationQueue.length > 0 && ((!this._busy && !this._inFullscreen) || notificationUrgent);
+        let notificationQueue = this._notificationQueue.filter(Lang.bind(this, function(notification) {
+            if (this._isScreenLocked)
+                return notification.showWhenLocked;
+            else
+                return true;
+        }));
+        let notificationUrgent = notificationQueue.length > 0 && notificationQueue[0].urgency == Urgency.CRITICAL;
+        // notificationsLimited is false when the screen is locked, because they go through
+        // different filtering, and we want to show non urgent messages at times
+        let notificationsLimited = (this._busy || this._inFullscreen) && !this._isScreenLocked;
+        let notificationsPending = notificationQueue.length > 0 && (!notificationsLimited || notificationUrgent);
+        let nextNotification = notificationQueue.length > 0 ? notificationQueue[0] : null;
         let notificationPinned = this._pointerInTray && !this._pointerInSummary && !this._notificationRemoved;
         let notificationExpanded = this._notificationBin.y < - this.actor.height;
-        let notificationExpired = (this._notificationTimeoutId == 0 && !(this._notification && this._notification.urgency == Urgency.CRITICAL) && !this._pointerInTray && !this._locked && !(this._pointerInKeyboard && notificationExpanded)) || this._notificationRemoved;
+        let notificationExpired = this._notificationTimeoutId == 0 &&
+                                  !(this._notification && this._notification.urgency == Urgency.CRITICAL) &&
+                                  !this._pointerInTray &&
+                                  !this._locked &&
+                                  !(this._pointerInKeyboard && notificationExpanded);
+        let notificationLockedOut = this._isScreenLocked && (this._notification && !this._notification.showWhenLocked);
+        let notificationMustClose = this._notificationRemoved || notificationLockedOut || notificationExpired;
         let canShowNotification = notificationsPending && this._summaryState == State.HIDDEN;
 
         if (this._notificationState == State.HIDDEN) {
-            if (canShowNotification)
-                this._showNotification();
+            if (canShowNotification) {
+                this._showNotification(nextNotification);
+                this._notificationQueue.splice(this._notificationQueue.indexOf(nextNotification), 1);
+            }
         } else if (this._notificationState == State.SHOWN) {
-            if (notificationExpired)
+            if (notificationMustClose)
                 this._hideNotification();
             else if (notificationPinned && !notificationExpanded)
                 this._expandNotification(false);
@@ -2120,7 +2165,7 @@ const MessageTray = new Lang.Class({
 
         let summaryOptionalInOverview = this._overviewVisible && !this._locked && !summaryHovered;
         let mustHideSummary = (notificationsPending && (notificationUrgent || summaryOptionalInOverview))
-                              || notificationsVisible;
+                              || notificationsVisible || this._isScreenLocked;
 
         if (this._summaryState == State.HIDDEN && !mustHideSummary) {
             if (summarySummoned) {
@@ -2237,8 +2282,8 @@ const MessageTray = new Lang.Class({
        }
     },
 
-    _showNotification: function() {
-        this._notification = this._notificationQueue.shift();
+    _showNotification: function(notification) {
+        this._notification = notification;
         this._unseenNotifications.push(this._notification);
         if (this._idleMonitorWatchId == 0)
             this._idleMonitorWatchId = this.idleMonitor.add_watch(1000,
@@ -2587,6 +2632,7 @@ const MessageTray = new Lang.Class({
             this._updateState();
     }
 });
+Signals.addSignalMethods(MessageTray.prototype);
 
 const SystemNotificationSource = new Lang.Class({
     Name: 'SystemNotificationSource',
@@ -2594,6 +2640,7 @@ const SystemNotificationSource = new Lang.Class({
 
     _init: function() {
         this.parent(_("System Information"), 'dialog-information', St.IconType.SYMBOLIC);
+        this.setTransient(true);
     },
 
     open: function() {
index b73f37203909c31b005a0421dbba9dde129a1080..925b2e631057bb117989217f7deddf93dafbf856 100644 (file)
@@ -75,6 +75,7 @@ const ShellInfo = new Lang.Class({
         let notification = null;
         if (this._source.notifications.length == 0) {
             notification = new MessageTray.Notification(this._source, text, null);
+            notification.setShowWhenLocked(true);
         } else {
             notification = this._source.notifications[0];
             notification.update(text, null, { clear: true });
index 1e3d7b95dcaf15e5deca78546dc2f521da23cb1d..7a8bd3434b822fb1707cfb8774879c224293c5ac 100644 (file)
@@ -11,6 +11,7 @@ const St = imports.gi.St;
 const GnomeSession = imports.misc.gnomeSession;
 const Lightbox = imports.ui.lightbox;
 const Main = imports.ui.main;
+const MessageTray = imports.ui.messageTray;
 const Tweener = imports.ui.tweener;
 
 const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver';
@@ -21,6 +22,8 @@ const CURTAIN_SLIDE_TIME = 1.2;
 // the slide up automatically
 const ARROW_DRAG_TRESHOLD = 0.4;
 
+const SUMMARY_ICON_SIZE = 48;
+
 const Clock = new Lang.Class({
     Name: 'ScreenShieldClock',
 
@@ -58,6 +61,191 @@ const Clock = new Lang.Class({
     }
 });
 
+const NotificationsBox = new Lang.Class({
+    Name: 'NotificationsBox',
+
+    _init: function() {
+        this.actor = new St.BoxLayout({ vertical: true,
+                                        name: 'screenShieldNotifications',
+                                        margin_top: 20
+                                      });
+
+        this._residentNotificationBox = new St.BoxLayout({ vertical: true,
+                                                           style_class: 'screen-shield-notifications-box' });
+        this._persistentNotificationBox = new St.BoxLayout({ vertical: true,
+                                                             style_class: 'screen-shield-notifications-box' });
+
+        this.actor.add(this._residentNotificationBox, { x_fill: true });
+        this.actor.add(this._persistentNotificationBox, { x_fill: false, x_align: St.Align.MIDDLE });
+
+        this._items = [];
+        Main.messageTray.getSummaryItems().forEach(Lang.bind(this, function(item) {
+            this._summaryItemAdded(Main.messageTray, item);
+        }));
+
+        this._summaryAddedId = Main.messageTray.connect('summary-item-added', Lang.bind(this, this._summaryItemAdded));
+    },
+
+    destroy: function() {
+        if (this._summaryAddedId) {
+            Main.messageTray.disconnect(this._summaryAddedId);
+            this._summaryAddedId = 0;
+        }
+
+        for (let i = 0; i < this._items.length; i++)
+            this._removeItem(this._items[i]);
+        this._items = [];
+
+        this.actor.destroy();
+    },
+
+    _updateVisibility: function() {
+        if (this._residentNotificationBox.get_n_children() > 0) {
+            this.actor.show();
+            return;
+        }
+
+        let children = this._persistentNotificationBox.get_children()
+        this.actor.visible = children.some(function(a) { return a.visible; });
+    },
+
+    _sourceIsResident: function(source) {
+        return source.hasResidentNotification() && !source.isChat;
+    },
+
+    _makeNotificationCountText: function(source) {
+        if (source.isChat)
+            return ngettext("%d new message", "%d new messages", source.count).format(source.count);
+        else
+            return ngettext("%d new notification", "%d new notifications", source.count).format(source.count);
+    },
+
+    _makeNotificationSource: function(source) {
+        let box = new St.BoxLayout({ style_class: 'screen-shield-notification-source' });
+
+        let iconClone = source.createIcon(SUMMARY_ICON_SIZE);
+        let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE);
+        sourceActor.setIcon(iconClone);
+        box.add(sourceActor.actor, { y_fill: true });
+
+        let textBox = new St.BoxLayout({ vertical: true });
+        box.add(textBox, { y_fill: true, expand: true });
+
+        let label = new St.Label({ text: source.title,
+                                   style_class: 'screen-shield-notification-label' });
+        textBox.add(label);
+
+        let countLabel = new St.Label({ text: this._makeNotificationCountText(source),
+                                        style_class: 'screen-shield-notification-count-text' });
+        textBox.add(countLabel);
+
+        box.visible = source.count != 0;
+        return [box, countLabel];
+    },
+
+    _summaryItemAdded: function(tray, item) {
+        // Ignore transient sources
+        if (item.source.isTransient)
+            return;
+
+        let obj = {
+            item: item,
+            source: item.source,
+            resident: this._sourceIsResident(item.source),
+            contentUpdatedId: 0,
+            sourceDestroyId: 0,
+            sourceBox: null,
+            countLabel: null,
+        };
+
+        if (obj.resident) {
+            item.prepareNotificationStackForShowing();
+            this._residentNotificationBox.add(item.notificationStackView);
+        } else {
+            [obj.sourceBox, obj.countLabel] = this._makeNotificationSource(item.source);
+            this._persistentNotificationBox.add(obj.sourceBox);
+        }
+
+        obj.contentUpdatedId = item.connect('content-updated', Lang.bind(this, this._onItemContentUpdated));
+        obj.sourceCountChangedId = item.source.connect('count-changed', Lang.bind(this, this._onSourceCountChanged));
+        obj.sourceDestroyId = item.source.connect('destroy', Lang.bind(this, this._onSourceDestroy));
+        this._items.push(obj);
+
+        this._updateVisibility();
+    },
+
+    _findSource: function(source) {
+        for (let i = 0; i < this._items.length; i++) {
+            if (this._items[i].source == source)
+                return i;
+        }
+
+        return -1;
+    },
+
+    _onItemContentUpdated: function(item) {
+        let obj = this._items[this._findSource(item.source)];
+        this._updateItem(obj);
+    },
+
+    _onSourceCountChanged: function(source) {
+        let obj = this._items[this._findSource(source)];
+        this._updateItem(obj);
+    },
+
+    _updateItem: function(obj) {
+        let itemShouldBeResident = this._sourceIsResident(obj.source);
+
+        if (itemShouldBeResident && obj.resident) {
+            // Nothing to do here, the actor is already updated
+            return;
+        }
+
+        if (obj.resident && !itemShouldBeResident) {
+            // make into a regular item
+            this._residentNotificationBox.remove_actor(obj.item.notificationStackView);
+
+            [obj.sourceBox, obj.countLabel] = this._makeNotificationSource(obj.source);
+            this._persistentNotificationBox.add(obj.sourceBox);
+        } else if (itemShouldBeResident && !obj.resident) {
+            // make into a resident item
+            obj.sourceBox.destroy();
+            obj.sourceBox = obj.countLabel = null;
+
+            obj.item.prepareNotificationStackForShowing();
+            this._residentNotificationBox.add(obj.item.notificationStackView);
+        } else {
+            // just update the counter
+            obj.countLabel.text = this._makeNotificationCountText(obj.item.source);
+            obj.sourceBox.visible = obj.source.count != 0;
+        }
+
+        this._updateVisibility();
+    },
+
+    _onSourceDestroy: function(source) {
+        let idx = this._findSource(source);
+
+        this._removeItem(this._items[idx]);
+        this._items.splice(idx, 1);
+
+        this._updateVisibility();
+    },
+
+    _removeItem: function(obj) {
+        if (obj.resident) {
+            this._residentNotificationBox.remove_actor(obj.item.notificationStackView);
+            obj.item.doneShowingNotificationStack();
+        } else {
+            obj.sourceBox.destroy();
+        }
+
+        obj.item.disconnect(obj.contentUpdatedId);
+        obj.source.disconnect(obj.sourceDestroyId);
+        obj.source.disconnect(obj.sourceCountChangedId);
+    },
+});
+
 /**
  * To test screen shield, make sure to kill gnome-screensaver.
  *
@@ -298,6 +486,13 @@ const ScreenShield = new Lang.Class({
 
         this._lockScreenGroup.add_actor(this._lockScreenContentsBox);
 
+        if (this._settings.get_boolean('show-notifications')) {
+            this._notificationsBox = new NotificationsBox();
+            this._lockScreenContentsBox.add(this._notificationsBox.actor, { x_fill: true,
+                                                                            y_fill: true,
+                                                                            expand: true });
+        }
+
         this._hasLockScreen = true;
     },
 
@@ -305,6 +500,11 @@ const ScreenShield = new Lang.Class({
         this._clock.destroy();
         this._clock = null;
 
+        if (this._notificationsBox) {
+            this._notificationsBox.destroy();
+            this._notificationsBox = null;
+        }
+
         this._lockScreenContentsBox.destroy();
 
         this._hasLockScreen = false;